// -------------------------------------------------
// BlinKit - BlinKit Library
// -------------------------------------------------
//   File Name: qjs_node.cpp
// Description: Bindings for Node
//      Author: Ziming Li
//     Created: 2022-03-08
// -------------------------------------------------
// Copyright (C) 2022 MingYang Software Technology.
// -------------------------------------------------

#include "./qjs_bindings.h"

#include "blinkit/blink/renderer/bindings/core/qjs/qjs.h"
#include "blinkit/blink/renderer/bindings/core/qjs/qjs_c_function_list_entries.h"
#include "blinkit/blink/renderer/bindings/core/script_controller.h"
#include "blinkit/blink/renderer/core/dom/element.h"

/**
 * https://developer.mozilla.org/en-US/docs/Web/API/Node
 */

namespace BlinKit {
namespace qjs {

namespace Strings {
static const char AppendChild[] = "appendChild";
static const char CloneNode[] = "cloneNode";
static const char InsertBefore[] = "insertBefore";
static const char InvalidChild[] = "Invalid child";
static const char NextSibling[] = "nextSibling";
static const char NodeType[] = "nodeType";
static const char ParentElement[] = "parentElement";
static const char ParentNode[] = "parentNode";
static const char RemoveChild[] = "removeChild";
static const char TextContent[] = "textContent";
}

static JSValue AppendChild(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
{
    Node *child = ScriptWrappable::SafeCast<Node>(argv[0]);
    if (nullptr == child)
        return JS_ThrowTypeError(ctx, Strings::InvalidChild);

    Node *node = ScriptWrappable::Cast<Node>(thisVal);

    Context c(*ctx);
    Node *ret = node->appendChild(child, c);
    if (c.hadException())
        return c.ReturnException();

    return ScriptController::From(ctx)->WrapNode(ret);
}

static JSValue CloneNode(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
{
    ASSERT(false); // BKTODO:
    return JS_UNDEFINED;
}

static JSValue GetNextSibling(JSContext *ctx, JSValueConst thisVal)
{
    Node *node = ScriptWrappable::Cast<Node>(thisVal);
    return ScriptController::From(ctx)->WrapNode(node->nextSibling());
}

static JSValue GetNodeType(JSContext *ctx, JSValueConst thisVal)
{
    Node *node = ScriptWrappable::Cast<Node>(thisVal);
    return JS_NewInt32(ctx, node->nodeType());
}

static JSValue GetParentElement(JSContext *ctx, JSValueConst thisVal)
{
    Node *node = ScriptWrappable::Cast<Node>(thisVal);
    return ScriptController::From(ctx)->WrapElement(node->parentElement());
}

static JSValue GetParentNode(JSContext *ctx, JSValueConst thisVal)
{
    Node *node = ScriptWrappable::Cast<Node>(thisVal);
    return ScriptController::From(ctx)->WrapNode(node->parentNode());
}

static JSValue GetTextContent(JSContext *ctx, JSValueConst thisVal)
{
    Node *node = ScriptWrappable::Cast<Node>(thisVal);
    return WrapString(ctx, node->textContent());
}

static JSValue InsertBefore(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
{
    Node *child = ScriptWrappable::SafeCast<Node>(argv[0]);
    if (nullptr == child)
        return JS_ThrowTypeError(ctx, Strings::InvalidChild);

    Node *ref;
    if (JS_IsObject(argv[1]))
        ref = ScriptWrappable::SafeCast<Node>(argv[1]);
    else if (JS_IsNull(argv[1]))
        ref = nullptr;
    else
        return JS_ThrowTypeError(ctx, "Invalid reference node");

    Node *node = ScriptWrappable::Cast<Node>(thisVal);

    Context c(*ctx);
    Node *ret = node->insertBefore(child, ref, c);
    if (c.hadException())
        return c.ReturnException();

    return ScriptController::From(ctx)->WrapNode(ret);
}

static JSValue RemoveChild(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
{
    Node *child = ScriptWrappable::SafeCast<Node>(argv[0]);
    if (nullptr == child)
        return JS_ThrowTypeError(ctx, Strings::InvalidChild);

    Node *node = ScriptWrappable::Cast<Node>(thisVal);

    Context c(*ctx);
    GCRefPtr<Node> ret = node->removeChild(child, c);
    if (c.hadException())
        return c.ReturnException();

    return ScriptController::From(ctx)->WrapNode(ret.get());
}

static JSValue SetTextContent(JSContext *ctx, JSValueConst thisVal, JSValueConst textContent)
{
    Node *node = ScriptWrappable::Cast<Node>(thisVal);
    node->setTextContent(ToWTFString(ctx, textContent));
    return JS_UNDEFINED;
}

#ifdef BLINKIT_CRAWLER_ENABLED
JSValue CreateNodePrototypeForCrawler(JSContext *ctx, JSValue eventTargetPrototype)
{
    static const JSCFunctionListEntry Funcs[] = {
        CFunctionEntry(Strings::AppendChild, 1, AppendChild),
        CFunctionEntry(Strings::CloneNode, 1, CloneNode),
        CFunctionEntry(Strings::InsertBefore, 2, InsertBefore),
        CGetSetEntry(Strings::NextSibling, GetNextSibling, nullptr),
        CGetSetEntry(Strings::NodeType, GetNodeType, nullptr),
        CGetSetEntry(Strings::ParentElement, GetParentElement, nullptr),
        CGetSetEntry(Strings::ParentNode, GetParentNode, nullptr),
        CFunctionEntry(Strings::RemoveChild, 1, RemoveChild),
        CGetSetEntry(Strings::TextContent, GetTextContent, SetTextContent)
    };

    JSValue ret = JS_NewObjectProto(ctx, eventTargetPrototype);
    JS_SetPropertyFunctionList(ctx, ret, Funcs, std::size(Funcs));
    return ret;
}
#endif

#ifdef BLINKIT_UI_ENABLED
JSValue CreateNodePrototypeForUI(JSContext *ctx, JSValue eventTargetPrototype)
{
    static const JSCFunctionListEntry Funcs[] = {
        CFunctionEntry(Strings::AppendChild, 1, AppendChild),
        CFunctionEntry(Strings::CloneNode, 1, CloneNode),
        CFunctionEntry(Strings::InsertBefore, 2, InsertBefore),
        CGetSetEntry(Strings::NextSibling, GetNextSibling, nullptr),
        CGetSetEntry(Strings::NodeType, GetNodeType, nullptr),
        CGetSetEntry(Strings::ParentElement, GetParentElement, nullptr),
        CGetSetEntry(Strings::ParentNode, GetParentNode, nullptr),
        CFunctionEntry(Strings::RemoveChild, 1, RemoveChild),
        CGetSetEntry(Strings::TextContent, GetTextContent, SetTextContent)
    };

    JSValue ret = JS_NewObjectProto(ctx, eventTargetPrototype);
    JS_SetPropertyFunctionList(ctx, ret, Funcs, std::size(Funcs));
    return ret;
}
#endif

} // namespace qjs
} // namespace BlinKit
